﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
using System.ComponentModel;
using System.Timers;

namespace Clonkd.ScheduledTask
{
    /// <summary>
    /// Allows arbitrary units of work to executed on specified intervals.
    /// </summary>
    public class ScheduledTaskRunner : IDisposable
    {
        private static ScheduledTaskRunner instance;
        
        /// <summary>
        /// The singleton instance of the ScheduledTaskRunner for this application.
        /// </summary>
        public static ScheduledTaskRunner Instance
        {
            get
            {
                if (instance == null)
                {
                    instance = new ScheduledTaskRunner();
                }
                return instance;
            }
        }

        private List<ScheduledTask> _tasks;

        private Timer timer
        {
            get;
            set;
        }

        private ScheduledTaskRunner()
        {
            _tasks = new List<ScheduledTask>();
            timer = new Timer(60 * 1000);
            timer.Elapsed += new ElapsedEventHandler(RunTasks);
            timer.Enabled = false;
        }

        /// <summary>
        /// An optional logging object that will be given the results of a scheduled task processing.
        /// </summary>
        public IScheduledTaskResultLogger Logger
        {
            get;
            set;
        }

        /// <summary>
        /// Enable the ScheduledTaskRunner to run tasks.
        /// </summary>
        public void Start()
        {
            timer.Enabled = true;
        }

        /// <summary>
        /// Stop the processing of tasks.
        /// </summary>
        public void Stop()
        {
            timer.Enabled = false;
        }

        /// <summary>
        /// Gets a value indicating whether the ScheduledTaskRunner is able to run tasks.  Controlled by Start() and Stop().
        /// </summary>
        public bool IsRunning
        {
            get { return timer.Enabled; }
        }

        /// <summary>
        /// Schedules a task.
        /// </summary>
        /// <param name="taskName">The name of the task to schedule.</param>
        /// <param name="executionInterval">How often the task should be executed</param>
        /// <param name="work">The code to run on the interval specified by <paramref name="executionInterval"/></param>
        public void ScheduleTask(string taskName, TimeSpan executionInterval, IExecution work)
        {
            ScheduleTask(new ScheduledTask(taskName, executionInterval)
            {
                Work = work
            });
        }

        /// <summary>
        /// Schedules a task.
        /// </summary>
        /// <param name="task">The ScheduledTask object to schedule.</param>
        public void ScheduleTask(ScheduledTask task)
        {
            ScheduleTask(task, false);
        }

        /// <summary>
        /// Schedules a task.  Can throw an exception if a task with the same name is already scheduled.
        /// </summary>
        /// <param name="task">The ScheduledTask object to schedule.</param>
        /// <param name="throwExceptionIfAlreadyScheduled">Whether or not to throw an exception if a task with the same name is already scheduled.</param>
        public void ScheduleTask(ScheduledTask task, bool throwExceptionIfAlreadyScheduled)
        {
            if (task == null)
            {
                throw new ArgumentNullException("task");
            }

            bool taskAlreadyScheduled = _tasks.Count(t => t.Name == task.Name) == 0;
            if (!taskAlreadyScheduled)
            {
                if (task.TaskExecutionInterval.TotalSeconds < 60)
                {
                    throw new Exception("A ScheduledTask should not have an execution interval of less than every 60 seconds.");
                }

                _tasks.Add(task);
            }

            if (throwExceptionIfAlreadyScheduled && taskAlreadyScheduled)
            {
                throw new ArgumentException(string.Format("The task named '{0}' has already been scheduled.", task.Name), "task");
            }
        }

        /// <summary>
        /// Removes a task from the execution queue.
        /// </summary>
        /// <param name="taskName">The name of the task to remove.</param>
        /// <returns>The ScheduledTask removed from the execution queue.</returns>
        public ScheduledTask UnScheduleTask(string taskName)
        {
            var task = _tasks.SingleOrDefault(t => t.Name == taskName);
            if (task != null)
            {
                _tasks.Remove(task);
            }

            return task;
        }

        /// <summary>
        /// A collection of the tasks scheduled on the exection queue.
        /// </summary>
        public IEnumerable<ScheduledTask> Tasks
        {
            get { return _tasks; }
        }

        private void RunTasks(object sender, ElapsedEventArgs e)
        {
            for (int x = 0; x < _tasks.Count; x++)
            {
                if (_tasks[x].ShouldRun())
                {
                    Work(_tasks[x]);
                }
            }
        }

        private void Work(ScheduledTask task)
        {
            BackgroundWorker w = new BackgroundWorker();
            task.IsRunning = true;
            w.DoWork += WorkHandler;
            w.RunWorkerCompleted += WorkCompleted;
            w.RunWorkerAsync(task);
        }

        private void WorkHandler(object sender, DoWorkEventArgs e)
        {
            var task = e.Argument as ScheduledTask;
            var timeStarted = DateTime.Now;

            if (task != null)
            {
                task.DateLastRun = timeStarted;

                var workResults = task.Work.Execute(new NoExecutionContext(task.DateLastRun, task.DateLastRun != DateTime.MinValue));

                ScheduledTaskRunResult runResults = new ScheduledTaskRunResult()
                {
                    TaskName = task.Name,
                    DateCompleted = DateTime.Now,
                    DateRun = timeStarted,
                    WorkResult = workResults
                };

                e.Result = runResults;
            }
        }

        private void WorkCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            var result = e.Result as ScheduledTaskRunResult;
            
            if (result != null)
            {
                var task = _tasks.SingleOrDefault(t => t.Name == result.TaskName);
                task.DateLastRun = result.DateRun;
                task.IsRunning = false;

                if (Logger != null)
                {
                    Logger.LogResult(result);
                }
            }

            var worker = sender as BackgroundWorker;
            worker.Dispose();
        }

        #region IDisposable Members

        public void Dispose()
        {
            timer.Dispose();
        }

        #endregion
    }
}
